// Used-executed commands part of HaxServ
//
// Written by: Test_User <hax@andrewyu.org>
//
// This is free and unencumbered software released into the public
// domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "types.h"
#include "table.h"
#include "commands.h"
#include "network.h"
#include "tls.h"
#include "config.h"
#include "utils.h"

#define MAX_SPAM_COUNT 65536

struct table user_commands = {0};

int help_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	for (uint64_t i = 0; i < user_commands.len; i++) {
		struct command_def *def = user_commands.array[i].ptr;

		struct string message[] = {
			command_prefix,
			user_commands.array[i].name,
			STRING("\x0F" " "),
			def->summary,
		};

		privmsg(STRING("1HC000000"), to, sizeof(message)/sizeof(*message), message);
	}

	return 0;
}
static struct command_def help_command_def = {
	.func = help_command,
	.privs = {0},
	.local_only = 0,
	.summary = STRING("Shows a list of commands, or, when I write it up, help about a specific command"),
};

int raw_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	if (argv[0].len < original_message.len) {
		original_message.data += argv[0].len + 1;
		original_message.len -= argv[0].len + 1;
		SEND(original_message);
	}
	SEND(STRING("\n"));

	return 0;
}
static struct command_def raw_command_def = {
	.func = raw_command,
	.privs = STRING("NetAdmin"), // TODO: Make this configurable elsewhere
	.local_only = 0,
	.summary = STRING("Sends a raw message to the server"),
};

static struct pref_type_suff {
	struct string pref;
	uint8_t type;
	struct string suff;
} sus_strings[] = {
	{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :Andrew is very sus.\n")},
	{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :I was the impostor, but you only know because I killed you.\n")},
	{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :\\x1b(0\n")},
	{STRING(":1HC000000 KILL "), 1, STRING(" :Ejected (1 Impostor remains)\n")},
	{STRING(":1HC000000 KILL "), 1, STRING(" :Ejected, and the crewmates have won.\n")},
}, cr_strings[] = {
	{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :You are now a cruxian toxicpod, kill the sharded crewmates.\n")},
	{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :You are now a cruxian omura, kill the sharded crewmates.\n")},
	{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :You are now a cruxian oct, but you ran out of reactors.\n")},
	{STRING(":1HC000000 KILL "), 1, STRING(" :Eliminated (You became a cruxian eclipse, but were drawn to my bait reactor)\n")},
	{STRING(":1HC000000 PRIVMSG "), 0, STRING(" :You attempted to change into a cruxian navanax, but were caught in the act.\n")},
};

int sus_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	uint64_t index = (uint64_t)random() % (sizeof(sus_strings)/sizeof(sus_strings[0]));

	SEND(sus_strings[index].pref);
	if (sus_strings[index].type == 0)
		SEND(to);
	else
		SEND(sender);
	SEND(sus_strings[index].suff);

	return 0;
}
static struct command_def sus_command_def = {
	.func = sus_command,
	.privs = {0},
	.local_only = 0,
	.summary = STRING("You seem a bit sus today"),
};

int cr_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	uint64_t index = (uint64_t)random() % (sizeof(cr_strings)/sizeof(cr_strings[0]));

	SEND(cr_strings[index].pref);
	if (cr_strings[index].type == 0)
		SEND(to);
	else
		SEND(sender);
	SEND(cr_strings[index].suff);

	return 0;
}
static struct command_def cr_command_def = {
	.func = cr_command,
	.privs = {0},
	.local_only = 0,
	.summary = STRING("Join the crux side"),
};

int spam_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	if (argc < 3) {
		privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
		return 0;
	}

	char err;
	uint64_t count = str_to_unsigned(argv[1], &err);
	if (err || (count > MAX_SPAM_COUNT && !is_local)) {
		privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Unknown number or number exceeds limit.")});
		return 0;
	}

	char wasspace = 1;
	uint64_t offset = 0;
	char found = 0;
	for (; offset < original_message.len; offset++) {
		if (original_message.data[offset] == ' ' && !wasspace)
			found++;

		wasspace = (original_message.data[offset] == ' ');

		if (found >= 2 && !wasspace)
			break;
	}

	if (found < 2) {
		WRITES(2, STRING("WARNING: Apparently there was no second argument... shouldn't happen.\n"));
		return 0;
	}

	struct command_def *cmd = get_table_index(user_commands, argv[2]);
	if (cmd) {
		if (!cmd->local_only) {
			if (cmd->privs.len != 0 && sender.len != 3) {
				struct user_info *user = get_table_index(user_list, sender);
				if (!user)
					return 1; // really shouldn't happen
				if (!STRING_EQ(user->opertype, cmd->privs)) {
					SEND(STRING(":1HC000000 NOTICE "));
					SEND(to);
					SEND(STRING(" :You are not authorized to execute that command.\n"));

					return 0;
				}

				if (cmd->func == spam_command) {
					SEND(STRING(":1HC000000 NOTICE "));
					SEND(to);
					SEND(STRING(" :Spam recursion is not allowed. The limit is for your own sake, please do not violate it.\n"));

					return 0;
				}
			}
		} else {
			SEND(STRING(":1HC000000 NOTICE "));
			SEND(to);
			SEND(STRING(" :Spamming of local-only commands is disabled.\n"));

			return 0;
		}
	} else {
		SEND(STRING(":1HC000000 NOTICE "));
		SEND(to);
		SEND(STRING(" :Unknown command.\n"));

		return 0;
	}

	struct string fake_original_message = {.data = original_message.data + offset, .len = original_message.len - offset};
	WRITES(2, fake_original_message);
	WRITES(2, STRING("\n"));

	for (uint64_t i = 0; i < count; i++) {
		int ret = cmd->func(sender, fake_original_message, to, argc - 2, &(argv[2]), 0);
		if (ret)
			return ret;
	}

	return 0;
}
static struct command_def spam_command_def = {
	.func = spam_command,
	.privs = STRING("NetAdmin"),
	.local_only = 0,
	.summary = STRING("Repeats a command <n> times"),
};

int clear_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	if (argc < 2) {
		privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
		return 0;
	}

	struct channel_info *channel = get_table_index(channel_list, argv[1]);
	if (!channel) {
		privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("That channel doesn't seem to exist, so is thereby already cleared.")});
		return 0;
	}

	for (uint64_t i = 0; i < channel->user_list.len; i++) {
		// TODO: Proper kick function, will prob come as a part of my next HaxServ rewrite

		SEND(STRING(":1HC000000 KICK "));
		SEND(argv[1]);
		SEND(STRING(" "));
		SEND(channel->user_list.array[i].name);
		SEND(STRING(" :(B\n"));

		if (client_connected) {
			SENDCLIENT(STRING(":"));
			SENDCLIENT(nick);
			SENDCLIENT(STRING("!"));
			SENDCLIENT(nick);
			SENDCLIENT(STRING("@"));
			SENDCLIENT(hostmask);
			SENDCLIENT(STRING(" KICK "));
			SENDCLIENT(argv[1]);
			SENDCLIENT(STRING(" "));

			struct user_info *user = get_table_index(user_list, channel->user_list.array[i].name);
			if (user)
				SENDCLIENT(user->nick);
			else
				SENDCLIENT(channel->user_list.array[i].name);

			SENDCLIENT(STRING(" :(B\r\n"));
		}
	}
	clear_table(&(channel->user_list));

	return 0;
}
static struct command_def clear_command_def = {
	.func = clear_command,
	.privs = STRING("NetAdmin"),
	.local_only = 0,
	.summary = STRING("Clears a channel"),
};

struct sh_command_args {
	char *command;
	struct string to;
};
void * async_sh_command(void *tmp) {
	struct sh_command_args *sh_args = tmp;

	FILE *f = popen(sh_args->command, "r");
	free(sh_args->command);

	char *line = NULL;
	size_t buflen;
	while (1) {
		ssize_t len = getline(&line, &buflen, f);
		if (len <= 0)
			break;

		struct string linestr = {.data = line, .len = (size_t)(len)};
		while (linestr.len > 0 && (linestr.data[linestr.len-1] == '\n' || linestr.data[linestr.len-1] == '\r'))
			linestr.len--;
		if (linestr.len == 0)
			linestr = STRING(" ");
		pthread_mutex_lock(&send_lock);
		SEND(STRING(":1HC000000 PRIVMSG "));
		SEND(sh_args->to);
		SEND(STRING(" :"));
		SEND(linestr);
		SEND(STRING("\n"));

		SENDCLIENT(STRING(":"));
		SENDCLIENT(nick);
		SENDCLIENT(STRING("!e@e PRIVMSG "));
		SENDCLIENT(sh_args->to);
		SENDCLIENT(STRING(" :"));
		SENDCLIENT(linestr);
		SENDCLIENT(STRING("\r\n"));
		pthread_mutex_unlock(&send_lock);
	}

	free(line);

	pclose(f);

	free(sh_args->to.data);
	free(sh_args);

	return 0;
}

int sh_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	if (!is_local) {
		return 0;
	}

	if (argc < 2) {
		privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
		return 0;
	}

	char wasspace = 1;
	uint64_t offset = 0;
	char found = 0;
	for (; offset < original_message.len; offset++) {
		if (original_message.data[offset] == ' ' && !wasspace)
			found++;

		wasspace = (original_message.data[offset] == ' ');

		if (found >= 1 && !wasspace)
			break;
	}

	if (found < 1) {
		WRITES(2, STRING("WARNING: Apparently there was no argument... shouldn't happen.\n"));
		return 0;
	}

	struct string command = {.data = original_message.data + offset, .len = original_message.len - offset};

	struct sh_command_args *sh_args;
	sh_args = malloc(sizeof(*sh_args));
	if (!sh_args) {
		WRITES(2, STRING("ERROR: OOM\n"));
		return 0;
	}

	sh_args->command = malloc(command.len+1);
	if (!sh_args->command) {
		free(sh_args);
		WRITES(2, STRING("ERROR: OOM\n"));
		return 0;
	}
	memcpy(sh_args->command, command.data, command.len);
	sh_args->command[command.len] = '\0';

	sh_args->to.len = to.len;
	sh_args->to.data = malloc(to.len);
	if (!sh_args->to.data) {
		free(sh_args->command);
		free(sh_args);
		WRITES(2, STRING("ERROR: OOM\n"));
		return 0;
	}
	memcpy(sh_args->to.data, to.data, to.len);

	pthread_t trash;
	pthread_create(&trash, NULL, async_sh_command, sh_args);

	return 0;
}
static struct command_def sh_command_def = {
	.func = sh_command,
	.privs = STRING("NetAdmin"),
	.local_only = 1,
	.summary = STRING("Executes a command locally"),
};

int kill_old_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	if (argc < 2) {
		privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
		return 0;
	}

	uint64_t current_time = (uint64_t)time(0);
	char err;
	uint64_t age = str_to_unsigned(argv[1], &err);
	if (err) {
		privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Invalid age!")});
		return 0;
	}
	if (age >= current_time)
		age = 0;
	else
		age = current_time - age;

	for (size_t i = 0; i < user_list.len; i++) {
		struct user_info *user = user_list.array[i].ptr;
		if ((user->user_ts <= age || STRING_EQ(user->nick, STRING("OperServ"))) && !STRING_EQ(user->server, STRING("1HC"))) {
			SEND(STRING(":1HC000000 KILL "));
			SEND(user_list.array[i].name);
			SEND(STRING(" :Your connection is too old.\n"));
		}
	}

	return 0;
}
static struct command_def kill_old_command_def = {
	.func = kill_old_command,
	.privs = STRING("NetAdmin"),
	.local_only = 0,
	.summary = STRING("Kills old connections (with a time you specify), and OperServ because OperServ is wrong"),
};

int echo_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	if (argc < 2) {
		privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
		return 0;
	}

	char wasspace = 1;
	uint64_t offset = 0;
	char found = 0;
	for (; offset < original_message.len; offset++) {
		if (original_message.data[offset] == ' ' && !wasspace)
			found++;

		wasspace = (original_message.data[offset] == ' ');

		if (found >= 1 && !wasspace)
			break;
	}

	if (found < 1) {
		WRITES(2, STRING("WARNING: Apparently there was no second argument... shouldn't happen.\n"));
		return 0;
	}

	struct string message[] = {{.data = original_message.data + offset, .len = original_message.len - offset}};
	privmsg(STRING("1HC000000"), to, 1, message);

	return 0;
}
static struct command_def echo_command_def = {
	.func = echo_command,
	.privs = {0},
	.local_only = 0,
	.summary = STRING("Repeats a message back"),
};

int tell_command(struct string sender, struct string original_message, struct string to, uint64_t argc, struct string *argv, char is_local) {
	if (argc < 3) {
		privmsg(STRING("1HC000000"), to, 1, (struct string[]){STRING("Missing args!")});
		return 0;
	}

	char wasspace = 1;
	uint64_t offset = 0;
	char found = 0;
	for (; offset < original_message.len; offset++) {
		if (original_message.data[offset] == ' ' && !wasspace)
			found++;

		wasspace = (original_message.data[offset] == ' ');

		if (found >= 2 && !wasspace)
			break;
	}

	if (found < 2) {
		WRITES(2, STRING("WARNING: Apparently there was no second argument... shouldn't happen.\n"));
		return 0;
	}

	struct string message[] = {{.data = original_message.data + offset, .len = original_message.len - offset}};
	privmsg(STRING("1HC000000"), argv[1], 1, message);

	return 0;
}
static struct command_def tell_command_def = {
	.func = tell_command,
	.privs = STRING("NetAdmin"),
	.local_only = 0,
	.summary = STRING("Sends a message to a target"),
};

int init_user_commands(void) {
	srandom(time(NULL));

	user_commands.array = malloc(0);

	set_table_index(&user_commands, STRING(":"), &raw_command_def);
	set_table_index(&user_commands, STRING("sus"), &sus_command_def);
	set_table_index(&user_commands, STRING("cr"), &cr_command_def);
	set_table_index(&user_commands, STRING("help"), &help_command_def);
	set_table_index(&user_commands, STRING("spam"), &spam_command_def);
	set_table_index(&user_commands, STRING("clear"), &clear_command_def);
	set_table_index(&user_commands, STRING("sh"), &sh_command_def);
	set_table_index(&user_commands, STRING("kill_old"), &kill_old_command_def);
	set_table_index(&user_commands, STRING("echo"), &echo_command_def);
	set_table_index(&user_commands, STRING("tell"), &tell_command_def);

	return 0;
}
